package com.zboin.filemger.service.impl;

import com.zboin.filemger.service.FileManageService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * @author zhboom
 * @date 2025/7/10 22:38
 */
@Service
public class FileManageServiceImpl implements FileManageService {

    private final Logger logger = LoggerFactory.getLogger(FileManageServiceImpl.class);
    private static final String UPLOAD_DIR = "upload/chunks";
    private static final String MERGE_DIR = "upload/merge";
    private static final long MAX_CHUNK_SIZE = 10 * 1024 * 1024; // 10MB

    public ResponseEntity<String> uploadChunk(
            MultipartFile file,
            int chunkIndex,
            String fileMd5,
            String fileName
    ) {
        // 参数校验
        if (file == null || file.isEmpty()) {
            return ResponseEntity.badRequest().body("上传文件不能为空");
        }
        if (chunkIndex < 0) {
            return ResponseEntity.badRequest().body("分片索引不能为负数");
        }
        if (fileMd5 == null || fileMd5.trim().isEmpty()) {
            return ResponseEntity.badRequest().body("文件MD5不能为空");
        }
        if (fileName == null || fileName.trim().isEmpty()) {
            return ResponseEntity.badRequest().body("文件名不能为空");
        }

        // 文件大小限制
        if (file.getSize() > MAX_CHUNK_SIZE) {
            return ResponseEntity.badRequest().body("分片大小超过限制");
        }

        // 清理文件名，防止路径穿越
        fileName = sanitizeFileName(fileName);

        // 创建分片存储目录
        Path chunkDir = Paths.get(UPLOAD_DIR, fileMd5);
        try {
            if (!Files.exists(chunkDir)) {
                Files.createDirectories(chunkDir);
            }
        } catch (IOException e) {
            logger.error("创建分片目录失败: {}", chunkDir, e);
            return ResponseEntity.status(500).body("创建分片目录失败");
        }

        // 构建分片文件路径
        Path chunkPath = chunkDir.resolve(fileName + "-" + chunkIndex + ".part");

        // 保存分片文件
        try {
            file.transferTo(chunkPath);
        } catch (IOException e) {
            logger.error("保存分片文件失败: {}", chunkPath, e);
            return ResponseEntity.status(500).body("保存分片文件失败");
        }

        return ResponseEntity.ok("分片接收成功");
    }

    @Override
    public ResponseEntity<String> mergeChunks(String fileMd5, String fileName) {
        // 参数校验
        if (fileMd5 == null || fileMd5.trim().isEmpty()) {
            return ResponseEntity.badRequest().body("文件MD5不能为空");
        }
        if (fileName == null || fileName.trim().isEmpty()) {
            return ResponseEntity.badRequest().body("文件名不能为空");
        }

        // 构建分片存储目录
        Path chunkDir = Paths.get(UPLOAD_DIR, fileMd5);
        if (!Files.exists(chunkDir)) {
            return ResponseEntity.status(404).body("分片目录不存在");
        }
        Path outputDir = Paths.get(MERGE_DIR, fileMd5);
        if (!Files.exists(outputDir)) {
            try {
                Files.createDirectories(outputDir);
            } catch (IOException e) {
                logger.error("创建合并目录失败: {}", outputDir, e);
                return ResponseEntity.status(500).body("创建合并目录失败");
            }
        }
        fileName = sanitizeFileName(fileName);
        Path outputFile = outputDir.resolve(fileName);
        if (!Files.exists(outputFile)) {
            try {
                Files.createFile(outputFile);
            } catch (IOException e) {
                logger.error("创建合并文件失败: {}", outputFile, e);
                return ResponseEntity.status(500).body("创建合并文件失败");
            }
        }

        try (OutputStream os = Files.newOutputStream(outputFile, StandardOpenOption.CREATE, StandardOpenOption.APPEND)) {
            Files.list(chunkDir)
                    .filter(path -> path.toString().endsWith(".part"))
                    .sorted(Comparator.comparingInt(p -> {
                        String name = p.getFileName().toString();
                        return Integer.parseInt(name.substring(name.lastIndexOf('-') + 1, name.lastIndexOf('.')));
                    }))
                    .forEach(chunk -> {
                        try {
                            Files.copy(chunk, os);
                            Files.delete(chunk);
                        } catch (IOException e) {
                            throw new RuntimeException(e);
                        }
                    });
        } catch (IOException e) {
            logger.error("合并分片文件失败: {}", outputFile, e);
            return ResponseEntity.status(500).body("合并分片文件失败");
        }
        try {
            Files.delete(chunkDir);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return ResponseEntity.ok("文件合并成功");
    }

    @Override
    public ResponseEntity<List<Integer>> uploadStatus(String fileMd5) {
        Path chunkDir = Paths.get(UPLOAD_DIR, fileMd5);
        if (!Files.exists(chunkDir)) {
            return ResponseEntity.ok(Collections.emptyList());
        }

        try {
            return ResponseEntity.ok(Files.list(chunkDir)
                    .filter(path -> path.toString().endsWith(".part"))
                    .map(p -> {
                        String name = p.getFileName().toString();
                        return Integer.parseInt(name.substring(name.lastIndexOf('-') + 1, name.lastIndexOf('.')));
                    })
                    .toList());
        } catch (IOException e) {
            logger.error("获取分片上传状态失败: {}", chunkDir, e);
            return ResponseEntity.status(500).body(Collections.emptyList());
        }
    }

    /**
     * 清理文件名中的非法字符，防止路径穿越
     */
    private String sanitizeFileName(String fileName) {
        // 简单替换掉非法字符，可根据实际需求扩展
        return fileName.replaceAll("[\\\\/:*?\"<>|]", "_");
    }
}
